<?

/*------------------------------------------------------------
 | This Project is under GNU GPL ( General Public License )  |
 | See LICENSE file											 |
 | "SpotLight Framework", v0.5.1.0b							 |
 | 															 |
 | http://www.spotlight.cv.ua								 |
 |															 |
 | (c) Serhiy Zaporozhets', main@spotlight.cv.ua , 2008-2009 |
 ------------------------------------------------------------*/

if (Session::getSystemValue ( 'noticeShow', false ) == true) {
	ini_set ( 'display_errors', 1 );
	error_reporting ( E_ALL );
} else {
	ini_set ( 'display_errors', 0 );
}

/**
 * Load actions and interceptors. Manage routine it. 
 * 
 * @version since begin.
 * @author asmhack
 * @license GPL
 * @copyright Serhiy Zaporozhets'
 */
class SpotLight {
	
	private $actionName;
	
	private $action;
	
	private $actionReflect;
	
	private $forwad;
	
	/**
	 * Logger
	 * @var Log4php
	 * @deprecated
	 */
	private $log;
	
	/**
	 *
	 * @var unknown_type
	 * @deprecated not in use.
	 */
	private $content;
	
	public function __construct($actionName, $forward = false) {
		try {
			//Benchmark::start ( 'SpotLight' );
			

			if ($actionName == false) // may be ===false ??????? not tested yet
				throw new ConfigException ( 'Root Action not defined!' );
			$this->actionName = $actionName;
			$this->forwad = $forward;
			
			new ConfigReader ( );
			
			if ($forward == false)
				ErrorsCollector::getInstance ()->clear ();
			
			$this->log = Log4php::getInstance ( 'debug.log' );
			
			$this->process ();
			
		//Benchmark::stopDev ( 'SpotLight' );
		} catch ( ActionInvokeException $e ) {
			if (Session::getSystemValue ( 'dev', false ) == true)
				die ( ExceptionPage::getExceptionPage ( $e ) );
			else
				header ( "HTTP/1.0 404 Not Found" );
		} catch ( ConfigException $e ) {
			die ( ExceptionPage::getExceptionPage ( $e, 'Rename <strong>spotlight.default.xml</strong> to <strong>spotlight.xml</strong> in <i>"SpotLight/configs/"</i> folder.' ) );
		} catch ( DBException $e ) {
			die ( ExceptionPage::getExceptionPage ( $e, 'Select another Database driver or download it from <a href="http://www.spotlight.cv.ua">SpotLight home page</a>' ) );
		} catch ( FilterInvokeException $e ) {
			die ( ExceptionPage::getExceptionPage ( $e, 'Check <i>"SpotLight/configs/<strong>spotlight.xml</strong>"</i> file' ) );
		} catch ( LocalizatorException $e ) {
			die ( ExceptionPage::getExceptionPage ( $e, 'Check your localization files.' ) );
		} catch ( ValidatorInvokeException $e ) {
			die ( ExceptionPage::getExceptionPage ( $e, 'Validator class not found. Please check your action class (action name=<b>' . $actionName . '</b>) for right <i>@validate</i> annotation' ) );
		} catch ( FileInlcudeException $e ) {
			if (Session::getSystemValue ( 'dev', false ) == true)
				die ( ExceptionPage::getExceptionPage ( $e, 'Check file.' ) );
			else
				header ( "HTTP/1.0 404 Not Found" );
		}
	
	}
	
	private function process() {
		
		$package = ConfigReader::getInstance ();
		
		if ($package == null)
			throw new ConfigException ( 'Config error! Can\t load config.' );
			//header("HTTP/1.0 505 HTTP Version Not Supported");
		

		$action = $package->getAction ( $this->actionName );
		
		if ($action !== null) {
			$mathces = $action [1];
			$isFlow = $action [2];
			$action = $action [0];
			
			if (! $action->hasClass ()) {
				$this->processExecuteReturn ( null );
				return;
			}
			
			$className = $action->getClass ();
			$fileName = $action->getFile ();
			
			if (! preg_match_all ( '/\{(\d+)\}/', $fileName, $mtch )) {
				if (file_exists ( $fileName ))
					require_once ($fileName . '');
				else
					throw new ActionInvokeException ( 'File not found: <b>' . $fileName . '</b>' );
			} else {
				foreach ( $mtch [1] as $item )
					$fileName = str_replace ( '{' . $item . '}', $mathces [$item], $fileName );
				
				if (file_exists ( $fileName ))
					require_once ($fileName . '');
				else
					throw new ActionInvokeException ( 'File not found: <b>' . $fileName . '</b>' );
			}
			
			if (! preg_match_all ( '/\{(\d+)\}/', $className, $mtch )) {
				if (! class_exists ( $className ))
					throw new ActionInvokeException ( 'Class is not found<b>' . $className . '</b> in file <b>' . $fileName . '</b>' );
			} else {
				foreach ( $mtch [1] as $item )
					$className = str_replace ( '{' . $item . '}', $mathces [$item], $className );
				
				if (! class_exists ( $className ))
					throw new ActionInvokeException ( 'Class is not found<b>' . $className . '</b> in file <b>' . $fileName . '</b>' );
			}
			
			$this->actionReflect = new ReflectionClass ( $className );
			
			$this->action = $this->actionReflect->newInstance ( $className, $this->actionName, $this->forwad );
			
			if (! is_subclass_of ( $this->action, 'Action' ))
				throw new ActionInvokeException ( 'Inheritance error! Your class <b>' . $className . '</b> must extends Action.' );
			
			$this->action->setAjaxAction ( $this->isAjaxReuqest () );
			
			$this->fillSetterMethods ();
			
			#processing before filters
			

			$flowName = ($isFlow === true ? $fn = substr ( $this->actionName, 0, strpos ( $this->actionName, '/' ) ) : null);
			
			$resGlbInterceptor = $this->processGlobalInterceptors ( $package->getInterceptorsForExecute ( 'before', $flowName, $this->actionName ) );
			
			if (! $resGlbInterceptor) {
				if (Session::getSystemValue ( 'dev', false ) == true)
					die ( ExceptionPage::getExceptionPageFromString ( 'Before global interceptor has return false.', 'Interceptor processing.' ) );
				else {
					header ( "HTTP/1.0 404 Not Found" );
					die ();
				}
			}
			
			$resActionInterceptor = $this->processActionInterceptors ( $action, true );
			if (! $resActionInterceptor) {
				if (Session::getSystemValue ( 'dev', false ) == true)
					die ( ExceptionPage::getExceptionPageFromString ( 'Before action interceptor h	as return false.', 'Interceptor processing.' ) );
				else {
					header ( "HTTP/1.0 404 Not Found" );
					die ();
				}
			}
			
			$this->action->before ();
			
			$resp = Response::getInstance ();
			
			if ((Session::getSystemValue ( 'saveLastAction', false ) == true && $resp->isOnLoadAction ())) {
				if ($this->isAjaxReuqest ())
					$this->action->noAjax ();
			} elseif (!$this->isAjaxReuqest ())
				$this->action->noAjax ();
			
			$method = $action->getMethod ();
			
			if (! preg_match_all ( '/\{(\d+)\}/', $method, $mtch )) {
				
				if (method_exists ( $this->action, $method ))
					$ret = $this->action->$method ();
				else
					throw new ActionInvokeException ( 'Method <b>' . $method . '</b> does not exist in <b>' . $this->actionName . '</b> action in file <b>' . $fileName . '</b>' );
			} else {
				
				foreach ( $mtch [1] as $item )
					$method = str_replace ( '{' . $item . '}', $mathces [$item], $method );
				
				if (method_exists ( $this->action, $method ))
					$ret = $this->action->$method ();
				else
					throw new ActionInvokeException ( 'Method <b>' . $method . '</b> does not exist in <b>' . $this->actionName . '</b> action in file <b>' . $fileName . '</b>' );
			}
			
			$this->action->setResult ( $ret );
			
			$this->action->after ();
			
			#processing after filters
			

			$this->processGlobalInterceptors ( $package->getInterceptorsForExecute ( 'after', $flowName, $this->actionName ) );
			$this->processActionInterceptors ( $action, false );
			
			# add class to work flow. that means that this class doesn't call 2 or more times per user request. (in other $this->forward($this->actionName) calls)
			$flow = WorkFlow::getInstance ();
			$flow->addClass ( $this->actionName );
			
			if ($this->actionName != '/' && Session::getSystemValue ( 'saveLastAction', false ) == true)
				$this->rememberLastAction ( $package, $flowName );
				
			#process action return
			$this->processExecuteReturn ( $ret );
		
		} else {
			if (Session::getSystemValue ( 'dev', false ) == true)
				echo (ExceptionPage::getExceptionPageFromString ( 'Action <b>' . $this->actionName . '</b> not found. 404 error', 'Action processing...' ));
			else {
				header ( "HTTP/1.0 404 Not Found" );
				die ();
			}
		}
	}
	
	private function rememberLastAction(&$pack, $flowName = null) {
		if ($this->forwad == false && ! $pack->isInExcludes ( $this->actionName, $flowName )) {
			Session::putSystemValue ( 'lastAction', $this->actionName, false );
			Session::putSystemValue ( 'lastActionReq', $_REQUEST );
		}
	}
	
	/**
	 * Force check if the action with type ajax(==true) or not ajax(==false) was invoked not by asynchronously.
	 *
	 * @param boolean $ajax
	 */
	private function checkRequestMethod($ajax = true) {
		if (Session::getSystemValue ( 'forceCheckHTTPReqType' ) == false)
			return;
		if (! $this->isAjaxReuqest () && $ajax) { # then set default resultType="ajax" and printing $ret;
			header ( "HTTP/1.0 505 HTTP Version Not Supported" );
			die (); # unreachable code
		}
	}
	private function isAjaxReuqest() {
		if (array_key_exists ( 'HTTP_X_REQUESTED_WITH', $_SERVER ))
			return strtolower ( $_SERVER ['HTTP_X_REQUESTED_WITH'] ) == 'xmlhttprequest';
		else
			return false;
	}
	
	/**
	 * Process global filters (in <spotlight>...</spotlight>)
	 *
	 * @param array(Filters) $filters
	 * @param Action $action=null if it's before filter
	 */
	private function processGlobalInterceptors($interceptors) {
		
		foreach ( $interceptors as $interceptor ) {
			
			$interceptorFileName = $interceptor->getFile ();
			$interceptorClassName = $interceptor->getClass ();
			
			if (file_exists ( $interceptorFileName ))
				@require_once ($interceptorFileName . '');
			else
				throw new FilterInvokeException ( 'Global Filter not found: <b>' . $interceptorFileName . '</b>' );
			
			if (! class_exists ( $interceptorClassName ))
				throw new FilterInvokeException ( 'Not found class <b>' . $interceptorClassName . '</b> in file <b>' . $interceptorFileName . '</b>' );
			
			$interceptorClass = new ReflectionClass ( $interceptorClassName );
			$interceptorInstance = $interceptorClass->newInstance ();
			
			if (! is_subclass_of ( $interceptorInstance, 'Interceptor' ))
				throw new FilterInvokeException ( 'Global Filter Inheritance error in file <b>' . $interceptorFileName . '</b>' );
			
			$interceptorResult = $interceptorInstance->execute ( $this->action );
			if ($interceptorResult === false)
				return false;
		}
		return true;
	}
	
	private function processActionInterceptors($action, $isBefore) {
		$interceptors = null;
		if ($isBefore && $action->hasBeforeInterceptors ())
			$interceptors = $action->getBeforeInterceptors ();
		elseif ($isBefore)
			return true;
		
		if (! $isBefore && $action->hasAfterInterceptors ())
			$interceptors = $action->getAfterInterceptors ();
		elseif (! $isBefore)
			return true;
		
		foreach ( $interceptors as $interceptor ) {
			
			$interceptorFileName = $interceptor->getFile ();
			$interceptorClassName = $interceptor->getClass ();
			
			if (file_exists ( $interceptorFileName ))
				@require_once ($interceptorFileName . '');
			else
				throw new FilterInvokeException ( 'Action Filter not found: <b>' . $interceptorFileName . '</b>' );
			
			if (! class_exists ( $interceptorClassName ))
				throw new FilterInvokeException ( 'Not found class <b>' . $interceptorClassName . '</b> in file <b>' . $interceptorFileName . '</b>' );
			
			$interceptorClass = new ReflectionClass ( $interceptorClassName );
			$interceptorInstance = $interceptorClass->newInstance ();
			
			if (! is_subclass_of ( $interceptorInstance, 'Interceptor' ))
				throw new FilterInvokeException ( 'Action Filter Inheritance error in file <b>' . $interceptorFileName . '</b>' );
			
			$interceptorResult = $interceptorInstance->execute ( $this->action );
			
			if ($interceptorResult === false)
				return false;
		}
		
		return true;
	
	}
	
	private function processExecuteReturn($ret) { # $ret=null
		

		$package = ConfigReader::getInstance ();
		if ($package == null)
			header ( "HTTP/1.0 505 HTTP Version Not Supported" );
		
		$actionColl = $package->getAction ( $this->actionName );
		
		if ($actionColl !== null) {
			$matches = $actionColl [1];
			
			$actionColl = $actionColl [0];
			#  .1 point! (if no className in action mapping defined)
			#		$ret==null
			if (! $actionColl->hasClass ()) { # action with no class name and only one result!!!
				$actionResult = $actionColl->getFirstResult ();
				$actionResultType = $actionColl->getFirstResultType ();
				if ($actionResultType == 'redirect')
					echo "<script>location=\"$actionResult\"</script>";
				else {
					if (! preg_match_all ( '/\{(\d+)\}/', $actionResult, $mtch )) {
						if (file_exists ( $actionResult ))
							require_once $actionResult;
						else
							throw new FileInlcudeException ( 'Can\'t find file ' . $actionResult );
					} else {
						foreach ( $mtch [1] as $item )
							$actionResult = str_replace ( '{' . $item . '}', $matches [$item], $actionResult );
						if (file_exists ( $actionResult ))
							require_once $actionResult;
						else
							throw new FileInlcudeException ( 'Can\'t find file ' . $actionResult );
					}
				
				}
				
				return; //exit from parsing;
			}
			
			#  .2 point! (Action with className in mapping defined.)
			

			#  .2.1 subpoint. if action has zero results!!!
			if (! $actionColl->hasResults ()) { # then set default resultType="ajax" and echoing $ret;
				#exmpl: if action return generated template. we gonna echo it!
				$this->checkRequestMethod ();
				if ($ret != null)
					echo $ret;
				else {
					# if this is a root Action in "FORWARD" chain
					if ($this->forwad == false) {
						$resp = Response::getInstance ();
						#if file
						if ($resp->responceIs ( 'file' )) {
							$resp->generateResponse ();
							return;
						}
						
						#if its "RSS" action
						if ($resp->responceIs ( 'rss' )) {
							echo $resp->generateResponse ();
							return;
						}
						if (! $this->isAjaxReuqest ()) {
							$this->action->noAjax ();
							$resp->setContentType ( 'html' );
						}
						echo $resp->generateResponse ();
					} elseif (! $this->isAjaxReuqest ()) {
						$this->action->noAjax ();
					}
				
				}
				return; //exit from parsing;
			}
			
			#  .2.2 subpoint. if action has 1 results (maybe unnamed)!!!
			if ($actionColl->getResultsAmount () == 1) {
				$actionResult = $actionColl->getFirstResult ();
				$actionResultType = $actionColl->getFirstResultType ();
				if ($actionResult == null) {
					if ($actionResultType == 'redirect')
						die ( 'Error: Action hasn\'t empty result for <b>"redirect"</b> type!' );
					if ($actionResultType == 'ajax') { # show generated in Action class content;
						$this->checkRequestMethod ();
						//echo $ret;
						if ($ret != null)
							echo $ret;
						else {
							if ($this->forwad == false) {
								$resp = Response::getInstance ();
								echo $resp->generateResponse ();
							}
						}
					}
					return;
				} else { # if result is preset
					if ($actionResultType == 'redirect')
						echo "<script>location=\"$actionResult\"</script>";
					else {
						if (! preg_match_all ( '/\{(\d+)\}/', $actionResult, $mtch ))
							require_once $actionResult;
						else {
							foreach ( $mtch [1] as $item )
								$actionResult = str_replace ( '{' . $item . '}', $matches [$item], $actionResult );
							require_once $actionResult;
						}
					
					}
					return; //exit from parsing;
				}
			} else {
				$actionResult = $actionColl->getResult ( $ret );
				$actionResultType = $actionColl->getResultType ( $ret );
				
				if ($actionResultType == 'redirect')
					echo "<script>location=\"$actionResult\"</script>";
				else {
					if (! preg_match_all ( '/\{(\d+)\}/', $actionResult, $mtch ))
						require_once $actionResult;
					else {
						foreach ( $mtch [1] as $item )
							$actionResult = str_replace ( '{' . $item . '}', $matches [$item], $actionResult );
						require_once $actionResult;
					}
				
				}
				
				return; //exit from parsing;
			}
			
			$result = $actionColl->getResult ( $ret );
			if ($result != null) {
				if ($actionColl->getResultType ( $ret ) == '' || $actionColl->getResultType ( $ret ) == 'redirect' || $actionColl->getResultType ( $ret ) == 'action')
					echo ("<script>location=\"$result\"</script>");
			}
		} else
			throw new ActionInvokeException ( 'Action is null for result: <b>' . $ret . '</b>' );
	}
	
	// $this->log->debug();
	private function fillSetterMethods() {
		try {
			
			$ai = new AnnotationInvoker ( $this->actionReflect, $this->action );
			
			if (Session::getSystemValue ( 'saveLastAction', false ) === true && $this->actionName == Session::getLastAction ()) {
				foreach ( $prevRequest as $key => $var ) {
					$this->action->$key = $ai->validate ( $key, $var );
				}
			}
			$reqVars = Request::getAttributes ();
			foreach ( $reqVars as $key => $var )
				$this->action->$key = $ai->validate ( $key, $var );
		} catch ( Exception $e ) {
			throw new ActionInvokeException ( 'Action setter invoke error.<hr/>' . $e->getMessage () );
		}
	}

}
?>